package business

import (
	"bufio"
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/x509"
	"encoding/base64"
	"encoding/pem"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path"
	"strconv"
	"strings"
	"time"

	common "example.com/m/com.common"
	qfcfg "example.com/m/com.qfcfg"
	qfmysql "example.com/m/com.qfmysql"
	qfredis "example.com/m/com.qfredis"
	"example.com/m/com.xorm_models/models"
	"example.com/m/internal/db"
	"example.com/m/internal/token"
	"example.com/m/util"
	"github.com/antchfx/htmlquery"
	"github.com/go-basic/uuid"
	_ "github.com/go-sql-driver/mysql"
	jsoniter "github.com/json-iterator/go"
	"github.com/kataras/iris/v12"
	"github.com/pkg/errors"
	"github.com/qf-tech/qftech-base/pkg/log"
	"github.com/qf-tech/qftech-base/pkg/qfcrypt"
	"go.uber.org/zap"
	"golang.org/x/net/html"
	"golang.org/x/sync/semaphore"
)

const (
	LockedTimes  int   = 7
	LockedPeriod int64 = 30 * 60

	Opreation   = "操作"
	RemoteLogin = "远程登录"
	UserRegist  = "用户注册"
)

type JSONResult struct {
	Status int    `json:"status"`
	Data   string `json:"data"`
}

type Controller struct{}

var sema *semaphore.Weighted

func init() {
	sema = semaphore.NewWeighted(int64(1))
}

// CheckTraceID def
func (control Controller) CheckTraceID(c iris.Context) {
	traceID := c.GetHeader(common.TraceID)
	clientIP, _ := getRemoteIP(c.Request())
	location, err := control.getIPLocation(c, clientIP)
	tag := "with"
	if traceID == "" {
		tmpSlice := strings.Split(uuid.New(), "-")
		traceID = tmpSlice[len(tmpSlice)-1]
		c.Request().Header.Set(common.TraceID, traceID)
		tag = "generate"
	}
	if err != nil && err != ErrNotEnableQueryLocation {
		log.Sugare.Errorf("Request %s trace id: %s, uri: %s method: %s, client ip: %s, get location err: %v", tag, traceID, c.Request().RequestURI, c.Method(), clientIP, err)
	} else if err == nil {
		log.Sugare.Infof("Request %s trace id: %s, uri: %s method: %s, client ip: %s, location: %s", tag, traceID, c.Request().RequestURI, c.Method(), clientIP, location)
	}

	ctx, _ := log.GetLogger().AddCtx(c.Request().Context(), zap.Any("TraceID", traceID))
	c.ResetRequest(c.Request().WithContext(ctx))
	c.Header(common.TraceID, traceID)
}

var ErrNotEnableQueryLocation = errors.New("not enable query location")

// getIPLocation def
func (control Controller) getIPLocation(c iris.Context, ip string) (string, error) {
	if !qfcfg.ServerConfig.Server.IPInfo.QueryLocation {
		return "", ErrNotEnableQueryLocation
	}

	location, err := qfredis.GetDigestInfo(c, ip)
	// redis 中未存储，则通过 API 查询 1次
	if err != nil && strings.Contains(err.Error(), "redigo: nil returned") {
		// 查询1次，查询成功则尝试存入 redis
		log.Sugare.Warnf("redis cache is none, get ip location from net, ip: %s", ip)
		location, err = getLocationInfo(ip)
		if err == nil && location != "" {
			if err := qfredis.SetDigestInfo(c, ip, location, 24*60*60); err != nil {
				log.Sugare.Errorf("set location info err: %v, ip: %s", err, ip)
			}
		}
		return location, err
	}

	if err != nil {
		return "", errors.Wrap(err, "query location from redis error")
	}

	return location, nil
}

func JSONError(c iris.Context, status int, errMsg string) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	c.StatusCode(status)

	type Err struct {
		Message string `json:"message"`
		Detail  string `json:"detail,omitempty"`
		Code    int    `json:"code,omitempty"`
	}

	type Response struct {
		Error Err `json:"error"`
	}

	resp := Response{
		Error: Err{
			Message: errMsg,
		},
	}

	if err := c.JSON(resp); err != nil {
		log.Errorf("Failed to encode: %v", err)
		return
	}

	retMsg, _ := jsoniter.Marshal(resp)
	log.Infof("response data: %s", string(retMsg))
}

func JSONResp(c iris.Context, status int, v interface{}) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	c.StatusCode(status)

	resp := map[string]interface{}{
		"data": v,
	}

	if err := c.JSON(resp); err != nil {
		log.Errorf("Failed to encode: %v", err)
		return
	}

	retMsg, _ := jsoniter.Marshal(resp)
	log.Infof("response data: %s", string(retMsg))
}

// get remote ip
func getRemoteIP(req *http.Request) (string, string) {
	remoteAddr := req.RemoteAddr
	port := "0"
	if ip := req.Header.Get("X-Real-IP"); ip != "" {
		remoteAddr = ip
	} else if ip = req.Header.Get("X-Forwarded-For"); ip != "" {
		remoteAddr = ip
	} else {
		remoteAddr, port, _ = net.SplitHostPort(remoteAddr)
	}

	if remoteAddr == "::1" {
		remoteAddr = "127.0.0.1"
	}

	return remoteAddr, port
}

// write log to db
func writeLogToDB(c iris.Context, majorType string, minorType string, optUser string, msg string) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	remoteIP, port := getRemoteIP(c.Request())
	ipInfo := fmt.Sprintf("%s:%s", remoteIP, port)
	ipInfoBase64Tmp := base64.StdEncoding.EncodeToString([]byte(ipInfo))

	logInfo := models.TLogInfo{
		VSessionId: strings.Replace(ipInfoBase64Tmp, "=", "", -1),
		VMajorType: majorType,
		VMinorType: minorType,
		VOptUser:   optUser,
		VClientIp:  remoteIP,
		VOptTime:   time.Now().Format(common.TimeFormatCMT),
		VOptInfo:   msg,
	}
	_, _ = qfmysql.InsertLogInfo(c, logInfo)
	log.Infof("major opt:%s, minor opt:%s, username:%s, %s, client addr:%s", majorType, minorType, optUser, msg, ipInfo)
}

type authRequest struct {
	Username  string `json:"username"`
	Pwd       string `json:"pwd"`
	Random    string `json:"random"`
	Timestamp string `json:"timestamp"`
	PCName    string `json:"pcname"`
	IP        string `json:"ip"`
}

func (control Controller) CheckReportToken(c iris.Context) error {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	var err error
	tokenTmp := c.URLParam("token")
	t := &token.TokenCreator{}
	log.Infof("query token: %s", tokenTmp)

	tokenStr := tokenTmp
	if len(tokenTmp) < 16 {
		if qfcfg.ServerConfig.Server.Auth.Enable > 0 {
			tokenStr, err = qfredis.GetDigestInfo(c, tokenTmp)
			if err != nil && strings.Contains(err.Error(), "redigo: nil returned") {
				log.Errorf("get token from redis is null, query token: %s", tokenTmp)
				return token.ErrTokenFmtInvalid
			}

			if err != nil {
				log.Errorf("get token from redis err: %v, query token: %s", err, tokenTmp)
				return errors.New("get token from redis err")
			}
		}
	}

	config := qfcfg.ServerConfig.Server.Report.TokenConfig
	if err := t.SetKey(config.Key).ValidHashToken(tokenStr); err != nil {
		return err
	}
	return nil
}

// AuthHandler http
func (control Controller) AuthHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	var req authRequest
	if err := util.TransJsonReq2Struct(c, &req); err != nil {
		log.Errorf("trans json to struct fail, err: %v", err)
		JSONError(c, http.StatusBadRequest, "trans json to struct error")
		return
	}

	userInfo, err := qfmysql.QueryUserInfo(c, req.Username)
	if err != nil {
		log.Errorf("username: %s noexist", req.Username)
		optInfo := fmt.Sprintf("登录失败, 用户不存在, PC-NAME: %s, %s", req.PCName, req.IP)
		writeLogToDB(c, Opreation, RemoteLogin, req.Username, optInfo)
		JSONResp(c, http.StatusOK, "noexist")
		return
	}
	log.Infof("username: %s info: %+v", req.Username, *userInfo)

	// 判断用户是否锁定
	u := models.TUser{UName: req.Username}
	if userInfo.UErrorCnt >= LockedTimes {
		if userInfo.ULockedTime != "" {
			formatTime, _ := time.Parse(common.TimeFormatCMT, userInfo.ULockedTime)
			nowTime, _ := time.Parse(common.TimeFormatCMT, time.Now().Format(common.TimeFormatCMT))
			remainPeriod := LockedPeriod - int64(nowTime.Sub(formatTime).Seconds())
			if remainPeriod > 0 {
				remainPeriodM := remainPeriod / 60
				remainPeriodS := remainPeriod % 60
				optInfo := fmt.Sprintf("被锁定, %dm%ds后解锁, PC-NAME: %s, %s", remainPeriodM, remainPeriodS, req.PCName, req.IP)
				writeLogToDB(c, Opreation, RemoteLogin, req.Username, optInfo)
				dataInfo := fmt.Sprintf("locked,%dm%ds", remainPeriodM, remainPeriodS)
				JSONResp(c, http.StatusOK, dataInfo)
				return
			}
			// 已超过锁定时长， 清空错误次数
			userInfo.UErrorCnt = 0
		}
	}

	// 普通密码验证
	pwdIsOk := (req.Pwd == userInfo.UPwd)

	// 后台支持http_login wsse 鉴权
	w := simpleWsse{
		username:  req.Username,
		nonce:     req.Pwd,
		random:    req.Random,
		timestamp: req.Timestamp,
	}
	retVal := checkWsseSimple(c, w)
	nonceIsOk := (retVal == "pass")
	log.Warnf("pwd: %s, check wsse retVal: %s nonceIsOk: %v pwdIsOk: %v", req.Pwd, retVal, nonceIsOk, pwdIsOk)

	if nonceIsOk || pwdIsOk {
		// 清空错误次数和锁定时间点
		u.UErrorCnt = 0
		u.ULockedTime = ""
		_, _ = qfmysql.UpdateUserInfo(c, u)
		optInfo := fmt.Sprintf("登录成功, PC-NAME: %s, %s", req.PCName, req.IP)
		writeLogToDB(c, Opreation, RemoteLogin, req.Username, optInfo)
		JSONResp(c, http.StatusOK, "pass")
		return
	}

	// 密码错误，更新错误次数和尝试登录时间
	u.UErrorCnt = userInfo.UErrorCnt + 1
	u.ULockedTime = time.Now().Format(common.TimeFormatCMT)
	_, _ = qfmysql.UpdateUserInfo(c, u)

	// 累计达到7次错误， 提示锁定
	if u.UErrorCnt >= LockedTimes {
		remainPeriodM := LockedPeriod / 60
		remainPeriodS := LockedPeriod % 60
		optInfo := fmt.Sprintf("被锁定, %dm%ds后解锁, PC-NAME: %s, %s", remainPeriodM, remainPeriodS, req.PCName, req.IP)
		writeLogToDB(c, Opreation, RemoteLogin, req.Username, optInfo)
		dataInfo := fmt.Sprintf("locked,%dm%ds", remainPeriodM, remainPeriodS)
		JSONResp(c, http.StatusOK, dataInfo)
		return
	}

	// 未锁定， 提示密码错误
	optInfo := fmt.Sprintf("登录失败, 密码错误, PC-NAME %s, %s", req.PCName, req.IP)
	writeLogToDB(c, Opreation, RemoteLogin, req.Username, optInfo)
	JSONResp(c, http.StatusOK, "error")
}

// path file existt
func pathFileExists(path string) (bool, error) {
	_, err := os.Stat(path)
	return err == nil, err
}

// RSADecrypt def
// cipherText 需要解密的byte数据
// path 私钥文件路径
func RSADecrypt(c context.Context, cipherText []byte, path string) []byte {
	log := log.GetLogger().GetCtx(c).Sugar()
	file, err := os.Open(path)
	if err != nil {
		log.Errorf("open file err: %v", err)
	}
	defer file.Close()

	info, _ := file.Stat()
	buf := make([]byte, info.Size())
	_, _ = file.Read(buf)
	// pem解码
	block, _ := pem.Decode(buf)
	// X509解码
	privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
	if err != nil {
		log.Errorf("parse pkcs1 key err: %v", err)
	}
	// 对密文进行解密
	plainText, _ := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherText)
	// 返回明文
	return plainText
}

type registRequest struct {
	Username  string `json:"username"`
	Pwd       string `json:"pwd"`
	Opt       string `json:"opt"`
	Algorithm string `json:"algorithm"`
	Level     string `json:"level"`
	PCName    string `json:"pcname"`
}

func (control Controller) checkRegistParam(req *registRequest) error {
	// opt 为空，表示注册，此时用户名和密码不能为空
	if req.Opt == "" && (req.Username == "" || req.Pwd == "") {
		return errors.New("regist username or password is empty")
	}

	if req.Opt != "" && req.Opt != "queryPubkey" {
		return errors.New("option invalid")
	}

	if req.Algorithm != "" && req.Algorithm != "RSA" {
		return errors.New("algorithm invalid")
	}

	if req.Level == "" {
		req.Level = "游客"
	}

	return nil
}

func (control Controller) getPubKeyCertData(c iris.Context, pubKeyCertPath string) (string, error) {
	if exist, _ := pathFileExists(pubKeyCertPath); !exist {
		return "", errors.New("pub_key_cert_not_exist")
	}

	f, err := os.Open(pubKeyCertPath)
	if err != nil {
		return "", errors.Wrap(err, "open public key cert err")
	}

	defer func() {
		f.Close()
	}()

	pubKeyCert, _ := ioutil.ReadAll(f)
	return string(pubKeyCert), nil
}

func (control Controller) decryptWithRSA(c iris.Context, encData string, priKeyCertPath string) (string, error) {
	encryptPwdTmp := encData // 传输过来的已base64编码
	// RSA私钥解密
	if exist, _ := pathFileExists(priKeyCertPath); !exist {
		return "", errors.New("pri_key_cert_not_exist")
	}

	encryptPwdReal := strings.Replace(encryptPwdTmp, "%2B", "+", -1)
	// 对上面的编码结果进行base64解码
	decodeBytes, err := base64.StdEncoding.DecodeString(encryptPwdReal)
	if err != nil {
		return "", errors.Wrap(err, "encrypt data base64 decode err")
	}
	decryptPwdBytes := RSADecrypt(c.Request().Context(), decodeBytes, priKeyCertPath)
	decryptPwd := string(decryptPwdBytes)
	if decryptPwd == "" {
		return "", errors.New("pwd_decrypt_fail")
	}
	return decryptPwd, nil
}

// RegistHandler http
func (control Controller) RegistHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	var req registRequest
	if err := util.TransJsonReq2Struct(c, &req); err != nil {
		log.Errorf("trans json to struct fail, err: %v", err)
		JSONError(c, http.StatusBadRequest, "trans json to struct error")
		return
	}

	if err := control.checkRegistParam(&req); err != nil {
		log.Errorf("check regist item err: %v", err)
		JSONError(c, http.StatusBadRequest, err.Error())
		return
	}

	// 返回RSA加密公钥
	if req.Opt == "queryPubkey" && req.Algorithm == "RSA" {
		content, err := control.getPubKeyCertData(c, "./pem/rsa_public_key.pem")
		if err != nil {
			optInfo := fmt.Sprintf("注册失败, %s, PC-NAME: %s", err.Error(), req.PCName)
			writeLogToDB(c, Opreation, UserRegist, req.Username, optInfo)
			JSONResp(c, http.StatusOK, err.Error())
			return
		}
		JSONResp(c, http.StatusOK, content)
		return
	}

	// 查询用户是否存在
	_, err := qfmysql.QueryUserInfo(c, req.Username)
	if err == nil {
		optInfo := fmt.Sprintf("注册失败, 用户已存在, PC-NAME:%s", req.PCName)
		writeLogToDB(c, Opreation, UserRegist, req.Username, optInfo)
		JSONResp(c, http.StatusOK, "exist")
		return
	}

	// 兼容客户端是否使用密码加密算法/RSA
	decryptPwd := req.Pwd
	if req.Algorithm == "RSA" {
		if decryptPwd, err = control.decryptWithRSA(c, req.Pwd, "./pem/rsa_private_key.pem"); err != nil {
			optInfo := fmt.Sprintf("注册失败, %s, PC-NAME: %s", err.Error(), req.PCName)
			writeLogToDB(c, Opreation, UserRegist, req.Username, optInfo)
			JSONResp(c, http.StatusOK, err.Error())
			return
		}
	}

	userInfo := models.TUser{UName: req.Username, UPwd: decryptPwd, ULevel: req.Level}
	optInfo := fmt.Sprintf("注册成功, PC-NAME: %s", req.PCName)
	result := "succeed"
	if _, err = qfmysql.InsertUserInfo(c, userInfo); err != nil {
		log.Errorf("resigt failed, username: %s, err: %v", req.Username, err)
		optInfo = fmt.Sprintf("注册失败, 未知错误, PC-NAME: %s", req.PCName)
		result = "failed"
	}
	writeLogToDB(c, Opreation, UserRegist, req.Username, optInfo)
	JSONResp(c, http.StatusOK, result)
}

var binPath string = ""

// checkWsse def
func checkWsse(c iris.Context) string {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	wsseMap := map[string]string{"Username": "", "PasswordDigest": "", "Nonce": "", "Created": ""}
	if qfcfg.ServerConfig.Server.Auth.Enable == 0 {
		return "pass"
	}

	wsseInfo := c.GetHeader("X-WSSE")
	if wsseInfo == "" {
		return "wsse check failed"
	}

	token := strings.Split(wsseInfo, " ")[0]
	if token != "UsernameToken" {
		return "wsse check failed"
	}

	pos := strings.Index(wsseInfo, token)
	wsseStr := string(wsseInfo[pos+len(token):])
	wsseList := strings.Split(wsseStr, ",")
	for _, item := range wsseList {
		item = strings.Trim(item, " ")
		itemList := strings.Split(item, "=")
		_, ok := wsseMap[itemList[0]]
		if ok {
			wsseMap[itemList[0]] = strings.Trim(itemList[1], "\"")
		}
	}

	for wsseKey := range wsseMap {
		if wsseMap[wsseKey] == "" {
			log.Errorf("wsse header:%s is blank", wsseKey)
			return "wsse check failed"
		}
	}

	mJSON, _ := jsoniter.Marshal(wsseMap)
	log.Infof("wsse data: %s", string(mJSON))

	username := wsseMap["Username"]
	digest := wsseMap["PasswordDigest"]
	nonce := wsseMap["Nonce"]
	created := wsseMap["Created"]

	createdTime, err := time.Parse("2006-01-02T15:04:05Z", created)
	if err != nil {
		log.Errorf("parse time string err: %s", err)
	}

	nowTmpTime := time.Now().Format(common.TimeFormatCMT) //time转string
	nowTime, _ := time.Parse(common.TimeFormatCMT, nowTmpTime)

	delta := int64(nowTime.Sub(createdTime).Seconds())
	if delta > int64(qfcfg.ServerConfig.Server.Auth.Redis.Timeout) {
		log.Errorf("created: %s out time, now time: %s, delta: %d", created, nowTmpTime, delta)
		return "outtime"
	}

	// 查询用户是否存在
	userInfo, err := qfmysql.QueryUserInfo(c, username)
	if err != nil {
		log.Errorf("user: %s noxist, err: %v", username, err)
		return "noexist"
	}
	password := userInfo.UPwd

	// sha256鉴权
	plainData := fmt.Sprintf("%s%s%s%s", username, password, nonce, created)
	digestCal := util.CalculateHash(plainData)
	if digest != digestCal {
		log.Errorf("PasswordDigest: %s error", digest)
		return "error"
	}

	val, err := qfredis.GetDigestInfo(c, digest)
	if err == nil {
		log.Errorf("repeat digest: %s error, val: %s", digest, val)
		return "repeat digest"
	}

	if err := qfredis.SetDigestInfo(c, digest, "1", qfcfg.ServerConfig.Server.Auth.Redis.Timeout); err != nil {
		log.Errorf("set digest info error: %v", err)
	}

	return "pass"
}

type simpleWsse struct {
	username  string
	nonce     string
	random    string
	timestamp string
}

func checkWsseSimple(c iris.Context, wsse simpleWsse) string {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	if wsse.username == "" || wsse.nonce == "" || wsse.random == "" || wsse.timestamp == "" {
		log.Error("wsse item is empty")
		return "wsse check failed"
	}

	timeTmp, _ := strconv.Atoi(wsse.timestamp)
	timeVal := int64(timeTmp)
	nowTime := time.Now().Unix()

	if timeVal-nowTime > int64(qfcfg.ServerConfig.Server.Auth.Redis.Timeout) ||
		nowTime-timeVal > int64(qfcfg.ServerConfig.Server.Auth.Redis.Timeout) {
		log.Errorf("created: %v out time, now time: %v, timeout: %d", timeVal, nowTime, qfcfg.ServerConfig.Server.Auth.Redis.Timeout)
		return "outtime"
	}

	// 查询用户是否存在
	userInfo, err := qfmysql.QueryUserInfo(c, wsse.username)
	if err != nil {
		log.Errorf("user: %s noxist, err: %v", wsse.username, err)
		return "noexist"
	}

	// sha256鉴权
	plainData := fmt.Sprintf("%s%s%s%s", wsse.username, userInfo.UPwd, wsse.random, wsse.timestamp)
	digestCal := util.CalculateHash(plainData)
	if wsse.nonce != digestCal {
		log.Errorf("PasswordDigest: %s error", wsse.nonce)
		return "error"
	}

	val, err := qfredis.GetDigestInfo(c, wsse.nonce)
	if err == nil {
		log.Errorf("repeat digest: %s error, val: %s", wsse.nonce, val)
		return "repeat digest"
	}

	if err := qfredis.SetDigestInfo(c, wsse.nonce, "1", qfcfg.ServerConfig.Server.Auth.Redis.Timeout); err != nil {
		log.Errorf("set digest info error: %v", err)
	}

	return "pass"
}

type cryptRequest struct {
	Data    string `json:"data"`
	Version string `json:"version"`
}

// EncryptHandler def
func (control Controller) EncryptHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	var req cryptRequest
	if err := util.TransJsonReq2Struct(c, &req); err != nil {
		log.Errorf("trans json to struct fail, err: %v", err)
		JSONError(c, http.StatusBadRequest, "trans json to struct error")
		return
	}

	if req.Data == "" {
		log.Error("data is empty")
		JSONError(c, http.StatusBadRequest, "data is empty")
		return
	}

	if req.Version == "" {
		req.Version = "QFTECH"
	}

	encData, err := qfcrypt.ConfigAes.Encrypt([]byte(req.Data), req.Version)
	if err != nil {
		log.Errorf("encrypt err: %v", err)
		JSONError(c, http.StatusBadRequest, "encrypt error")
		return
	}

	log.Infof("encrypt data: %s", encData)
	JSONResp(c, http.StatusOK, encData)
}

// DecryptHandler def
func (control Controller) DecryptHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	var req cryptRequest
	if err := util.TransJsonReq2Struct(c, &req); err != nil {
		log.Errorf("trans json to struct fail, err: %v", err)
		JSONError(c, http.StatusBadRequest, "trans json to struct error")
		return
	}

	if req.Data == "" {
		log.Error("data is empty")
		JSONError(c, http.StatusBadRequest, "data is empty")
		return
	}

	if req.Version == "" {
		req.Version = "QFTECH"
	}

	decData, err := qfcrypt.ConfigAes.Decrypt(req.Data, req.Version)
	if err != nil {
		log.Errorf("decrypt err: %v", err)
		JSONError(c, http.StatusBadRequest, "decrypt error")
		return
	}

	decDataStr := string(decData[:])
	log.Infof("decrypt data: %s", decDataStr)
	JSONResp(c, http.StatusOK, decDataStr)
}

// UpdateSign def
func (control Controller) UpdateSign(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	priKeyPath := c.URLParam("kp")
	if priKeyPath == "" {
		priKeyPath = "./pem/rsa_private_key.pem"
	}

	if exist, _ := pathFileExists("./sign"); !exist {
		log.Errorf("decrypt err: %v", "sign tool not exist")
		JSONError(c, http.StatusBadRequest, "sign tool not exist")
		return
	}

	cmd := fmt.Sprintf("./sign  -mode rsa -private-key-cert %s", priKeyPath)
	data := c.URLParam("data")
	if data != "" {
		cmd = fmt.Sprintf("%s -data %s", cmd, data)
	}

	ret := control.execShell(c.Request().Context(), cmd)
	var sign string
	tag := "generate sign: "
	start := strings.Index(ret, tag)
	end := strings.LastIndex(ret, "\n")
	if start > -1 && end > -1 {
		sign = ret[start+len(tag) : end]
	}

	log.Infof("update sign: %s", ret)
	JSONResp(c, http.StatusOK, sign)
}

// VerifySign def
func (control Controller) VerifySign(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	pubKeyPath := c.URLParam("kp")
	if pubKeyPath == "" {
		pubKeyPath = "./pem/rsa_public_key.pem"
	}

	if exist, _ := pathFileExists("./sign"); !exist {
		log.Errorf("decrypt err: %v", "sign tool not exist")
		JSONError(c, http.StatusBadRequest, "sign tool not exist")
		return
	}

	cmd := fmt.Sprintf("./sign  -mode rsa -verify  -public-key-cert %s", pubKeyPath)
	sign := c.URLParam("sign")
	if sign != "" {
		cmd = fmt.Sprintf("%s -sign %s", cmd, sign)
	}

	data := c.URLParam("data")
	if data != "" {
		cmd = fmt.Sprintf("%s -data %s", cmd, data)
	}

	ret := control.execShell(c.Request().Context(), cmd)
	result := strings.Contains(ret, "verify sign succeed")

	log.Infof("verify sign: %s, cmd: %s", ret, cmd)
	JSONResp(c, http.StatusOK, result)
}

func (control Controller) WsseHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	retVal := checkWsse(c)
	if retVal != "pass" {
		log.Errorf("check wsse: %s", retVal)
		JSONResp(c, http.StatusOK, retVal)
		return
	}
	c.Next()
}

type ReportToken struct {
	DirPath string `json:"dirPath"`
}

// UploadHandler http
func (control Controller) UploadHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	if c.Method() != "POST" {
		c.StatusCode(http.StatusNotFound)
		log.Error("404")
		return
	}
	uploadDirPth := c.GetHeader(common.UploadDirPath)
	if uploadDirPth == "" {
		uploadDirPth = "cava3/upload/"
	}
	reportType := c.GetHeader(common.CAVA3ReportType)

	retVal := checkWsse(c)
	if retVal != "pass" {
		log.Errorf("check wsse: %s", retVal)
		JSONResp(c, http.StatusOK, retVal)
		return
	}

	if c.Method() == "POST" {
		// 设置内存大小
		_ = c.Request().ParseMultipartForm(32 << 20)
		// 获取上传的第一个文件
		file, header, err := c.Request().FormFile("file")
		defer func() {
			file.Close()
		}()
		if err != nil {
			desc := fmt.Sprintf("error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		// 创建上传文件
		if binPath == "" {
			var err error
			binPath, err = os.Getwd()
			if err != nil {
				desc := fmt.Sprintf("error: %v", err)
				log.Errorf("%s", desc)
				JSONResp(c, http.StatusOK, desc)
				return
			}
			log.Infof("binPath: %s", binPath)
		}

		pos := strings.LastIndex("header.Filename", "/")
		basename := header.Filename[pos+1:]
		if !strings.HasSuffix(uploadDirPth, "/") {
			uploadDirPth = uploadDirPth + "/"
		}

		dirPath := qfcfg.ServerConfig.Server.FileSvr.Dir
		if strings.HasPrefix(dirPath, "./") {
			dirPath = strings.Split(dirPath, "./")[1]
		}

		if !strings.HasPrefix(uploadDirPth, "cava3/upload/") &&
			!strings.HasPrefix(uploadDirPth, "cava3/outputs/log/") &&
			!strings.HasPrefix(uploadDirPth, dirPath) {
			log.Errorf("upload dir path prefix invalid, it is: %s", uploadDirPth)
			JSONResp(c, http.StatusOK, "upload dir path prefix invalid")
			return
		}

		fileDirPath := binPath + "/" + uploadDirPth
		filePath := fileDirPath + basename
		log.Infof("file dir: %s, basename: %s", fileDirPath, basename)
		existFalg, _ := pathFileExists(filePath)

		// 目标目录文件存在，且不强制更新，返回文件已存在
		forceUpload := c.GetHeader("FORCE-UPLOAD")
		if forceUpload == "" || forceUpload == "FALSE" {
			if existFalg {
				log.Infof("file exist, filePath: %s", filePath)
				JSONResp(c, http.StatusOK, "file exist")
				return
			}
		} else if forceUpload == "TRUE" {
			if existFalg {
				err := os.Remove(filePath)
				if err != nil {
					desc := fmt.Sprintf("error: %v", err)
					log.Errorf("%s", desc)
					JSONResp(c, http.StatusOK, desc)
					return
				}
			}
		}

		// 确保上传的目标路径目录存在
		if err := os.MkdirAll(fileDirPath, os.ModePerm); err != nil {
			desc := fmt.Sprintf("mkdir all error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		cur, err := os.Create(filePath)
		defer func() {
			cur.Close()
		}()
		if err != nil {
			desc := fmt.Sprintf("error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		log.Infof("filePath: %s", filePath)
		// 把上传文件数据拷贝到我们新建的文件
		_, _ = io.Copy(cur, file)

		host := fmt.Sprintf("http://%s:%d", qfcfg.ServerConfig.Server.IP, qfcfg.ServerConfig.Server.Port)
		// 如果使能了http下载文件使用代理，则使用AgentHost
		if qfcfg.ServerConfig.Server.Enable != 0 {
			host = qfcfg.ServerConfig.Server.AgentHost
		}

		t := &token.TokenCreator{}
		reportToken := ReportToken{DirPath: uploadDirPth}
		config := qfcfg.ServerConfig.Server.Report.TokenConfig
		expire, _ := time.ParseDuration(config.Expire)

		tokenVal, err := t.SetKey(config.Key).SetExpired(expire).GenHashToken(reportToken)
		if err != nil {
			desc := fmt.Sprintf("error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		token := tokenVal
		if qfcfg.ServerConfig.Server.Auth.Enable > 0 {
			token = strings.ToUpper(util.GenShortUUID())
			if err := qfredis.SetDigestInfo(c, token, tokenVal, int(expire.Seconds())); err != nil {
				log.Errorf("set token to redis error: %v", err)
				JSONResp(c, http.StatusOK, err.Error())
				return
			}
		}

		if reportType == string(common.CAVA3Log) || reportType == string(common.CAVA3Report) {
			if err := changeReportLogLink(filePath, token); err != nil {
				log.Errorf("change report err: %v", err)
			}
		}
		msg := fmt.Sprintf("[INFO] file:%s upload succeed, path:%s/%s%s?token=%s",
			filePath, host, uploadDirPth, basename, token)
		JSONResp(c, http.StatusOK, msg)
	}
}

func changeReportLogLink(path string, token string) error {
	doc, err := htmlquery.LoadDoc(path)
	if err != nil {
		return errors.Wrap(err, "html query load src file err")
	}

	nodes, err := htmlquery.QueryAll(doc, "//script[@type='text/javascript']/text()")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}

	for i, n := range nodes {
		if strings.Contains(n.Data, "\"reportURL\":") || strings.Contains(n.Data, "\"logURL\":") {
			data := strings.TrimSuffix(strings.Split(n.Data, "=")[1], ";\n")
			var m map[string]interface{}
			_ = jsoniter.Unmarshal([]byte(data), &m)
			reportURL, ok := m["reportURL"]
			if ok {
				m["reportURL"] = fmt.Sprintf("%s?token=%s", reportURL, token)
			}
			logURL, ok := m["logURL"]
			if ok {
				m["logURL"] = fmt.Sprintf("%s?token=%s", logURL, token)
			}
			dealData, _ := jsoniter.Marshal(m)
			nodes[i].Data = fmt.Sprintf("window.settings = %s;\n", string(dealData))
			break
		}
	}
	// _ = os.Remove(path)
	file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		return errors.Wrap(err, "open file err")
	}
	_ = html.Render(file, doc)
	return nil
}

func rename(c context.Context, dirPath string, old string, new string, token string) (err error) {
	log := log.GetLogger().GetCtx(c).Sugar()

	srcFile := dirPath + "/" + old
	dstFile := dirPath + "/" + new
	f, err := os.Create(dstFile)
	if err != nil {
		return errors.Wrap(err, "create dst file err")
	}
	defer f.Close()

	w := bufio.NewWriter(f)

	file, err := os.Open(srcFile)
	if err != nil {
		return errors.Wrap(err, "open src file err")
	}
	defer file.Close()

	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := scanner.Text()
		// token 非空时，将 详细日志请查看文件 超链接添加上 token
		pos := strings.Index(line, "href=\"")
		pos1 := strings.Index(line, ".html\">")
		if -1 != pos && -1 != pos1 {
			href := line[pos : pos1+6]
			basename := line[pos+6 : pos1+5]
			if token != "" {
				new = fmt.Sprintf("href=\"%s?token=%s\"", basename, token)
				line = strings.ReplaceAll(line, href, new)
			}
			log.Warnf("basename: %s, line: %s", basename, line)
		}

		if strings.Contains(line, "name=\"generator\"") {
			line = "<meta name=\"generator\" content=\"cava3 convert\">"
		} else if strings.Contains(line, "name=\"created\"") {
			line = fmt.Sprintf("<meta name=\"created\" content=\"%s\">", time.Now().Format(time.RFC3339))
		} else if strings.Contains(line, "name=\"changed\"") {
			line = fmt.Sprintf("<meta name=\"changed\" content=\"%s\">", time.Now().Format(time.RFC3339))
		}

		fmt.Fprintln(w, line)
	}

	if err = w.Flush(); err != nil {
		return errors.Wrap(err, "flush src file err")
	}

	if err := scanner.Err(); err != nil {
		return errors.Wrap(err, "scan src file err")
	}

	return nil
}

func renamev2(c context.Context, dirPath string, old string, new string, token string) (err error) {
	srcFile := dirPath + "/" + old
	dstFile := dirPath + "/" + new
	doc, err := htmlquery.LoadDoc(srcFile)
	if err != nil {
		return errors.Wrap(err, "html query load src file err")
	}
	// token 非空时，将 详细日志请查看文件 超链接添加上 token
	aXpathList := []string{"//tr/td/font/a", "//tr/td/a"}
	for _, aXpath := range aXpathList {
		n, err := htmlquery.Query(doc, aXpath)
		if err != nil {
			return errors.Wrap(err, "html query all err")
		}

		if n != nil && token != "" {
			util.SetNodeAttr(n, "href", fmt.Sprintf("%s?token=%s", util.GetNodeAttr(n, "href"), token))
			break
		}
	}

	// change meta content attr by name which is generator、created、changed
	n, err := htmlquery.Query(doc, "//meta[@name='generator']")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}
	util.SetNodeAttr(n, "content", "cava3 convert")

	n, err = htmlquery.Query(doc, "//meta[@name='created']")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}
	util.SetNodeAttr(n, "content", time.Now().Format(time.RFC3339))

	n, err = htmlquery.Query(doc, "//meta[@name='changed']")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}
	util.SetNodeAttr(n, "content", time.Now().Format(time.RFC3339))

	// <meta http-equiv="content-type" content="text/html; charset=UTF-8"/>
	n, err = htmlquery.Query(doc, "//meta[@http-equiv='content-type']")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}
	util.SetNodeAttr(n, "content", "text/html; charset=UTF-8")

	// body add background-color background-color: rgb(199, 237, 204);
	n, err = htmlquery.Query(doc, "//body")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}
	util.SetNodeAttr(n, "style", "background-color: rgb(199, 237, 204);")

	// table add attr align=center
	n, err = htmlquery.Query(doc, "//table")
	if err != nil {
		return errors.Wrap(err, "html query all err")
	}
	util.SetNodeAttr(n, "align", "center")

	file, err := os.OpenFile(dstFile, os.O_RDWR|os.O_TRUNC|os.O_CREATE, 0644)
	if err != nil {
		return errors.Wrap(err, "open file err")
	}
	html.Render(file, doc)
	return nil
}

func transDocs2Pdf(c iris.Context, fileSrcPath string, fileOutDir string, converterType string, token string) (fileOutPath string, error error) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	// 校验fileSrcPath
	command := "libreoffice7.3"
	srcFile, err := os.Open(fileSrcPath)
	if err != nil && os.IsNotExist(err) {
		return "", errors.Wrap(err, "open file err")
	}
	// 如文件输出目录fileOutDir不存在则自动创建
	outFileDir, err := os.Open(fileOutDir)
	if err != nil && os.IsNotExist(err) {
		err := os.MkdirAll(fileOutDir, os.ModePerm)
		if err != nil {
			log.Errorf("File ouput dir create err: %v", err)
			return "", errors.Wrap(err, "mkdir dir err")
		}
	}
	// 关闭流
	defer func() {
		_ = srcFile.Close()
		_ = outFileDir.Close()
	}()

	cmd := exec.Command(command, "--invisible", "--convert-to", converterType,
		fileSrcPath, "--outdir", fileOutDir)
	byteByStat, err := cmd.Output()
	// 命令调用转换失败
	if err != nil {
		return "", errors.Wrap(err, "exec cmd err")
	}

	var tag string
	if !strings.HasSuffix(fileOutDir, "/") {
		tag = "/"
	}

	fileOutPath = fileOutDir + tag + strings.Split(path.Base(fileSrcPath), ".")[0]
	if converterType == "htm" {
		fileOutPath += ".htm"
		old := path.Base(fileOutPath)
		new := old + "l"
		if err := renamev2(c.Request().Context(), fileOutDir, old, new, token); err != nil {
			return "", errors.Wrap(err, "rename err")
		}
		_ = os.Remove(fileOutDir + "/" + old)
		fileOutPath += "l"
	} else {
		fileOutPath += ".pdf"
	}
	log.Infof("file convert ok, src path: %s, out path: %s, output: %s", fileSrcPath, fileOutPath, string(byteByStat))
	return fileOutPath, nil
}

// ConvertHandler http
func (control Controller) ConvertHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	if c.Method() != "POST" {
		c.StatusCode(http.StatusNotFound)
		log.Error("404")
		return
	}

	if !qfcfg.ServerConfig.Server.Convert.Enable {
		log.Warnf("file convert is disable")
		JSONResp(c, http.StatusBadRequest, "file convert is disable")
		return
	}

	uploadDirPth := c.GetHeader(common.UploadDirPath)
	if uploadDirPth == "" {
		uploadDirPth = "cava3/upload/"
	}

	outFromat := c.GetHeader("OUTPUT-FORMAT")
	if outFromat == "" {
		outFromat = "htm"
	}

	retVal := checkWsse(c)
	if retVal != "pass" {
		log.Errorf("check wsse: %s", retVal)
		JSONResp(c, http.StatusOK, retVal)
		return
	}

	if c.Method() == "POST" {
		// 设置内存大小
		_ = c.Request().ParseMultipartForm(32 << 20)
		// 获取上传的第一个文件
		file, header, err := c.Request().FormFile("file")
		defer func() {
			file.Close()
		}()
		if err != nil {
			desc := fmt.Sprintf("error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		// 创建上传文件
		if binPath == "" {
			var err error
			binPath, err = os.Getwd()
			if err != nil {
				desc := fmt.Sprintf("error: %v", err)
				log.Errorf("%s", desc)
				JSONResp(c, http.StatusOK, desc)
				return
			}
			log.Infof("binPath: %s", binPath)
		}

		pos := strings.LastIndex("header.Filename", "/")
		basename := header.Filename[pos+1:]
		if !strings.HasSuffix(uploadDirPth, "/") {
			uploadDirPth = uploadDirPth + "/"
		}
		if !strings.HasPrefix(uploadDirPth, "cava3/upload/") && !strings.HasPrefix(uploadDirPth, "cava3/outputs/log/") {
			log.Errorf("upload dir path prefix invalid, it is: %s", uploadDirPth)
			JSONResp(c, http.StatusOK, "upload dir path prefix invalid")
			return
		}

		fileDirPath := binPath + "/" + uploadDirPth
		filePath := fileDirPath + basename
		log.Infof("file dir: %s, basename: %s", fileDirPath, basename)
		existFalg, _ := pathFileExists(filePath)

		// 目标目录文件存在，且不强制更新，返回文件已存在
		forceUpload := c.GetHeader("FORCE-UPLOAD")
		if forceUpload == "" || forceUpload == "FALSE" {
			if existFalg {
				log.Infof("file exist, filePath: %s", filePath)
				JSONResp(c, http.StatusOK, "file exist")
				return
			}
		} else if forceUpload == "TRUE" {
			if existFalg {
				err := os.Remove(filePath)
				if err != nil {
					desc := fmt.Sprintf("error: %v", err)
					log.Errorf("%s", desc)
					JSONResp(c, http.StatusOK, desc)
					return
				}
			}
		}

		// 确保上传的目标路径目录存在
		if err := os.MkdirAll(fileDirPath, os.ModePerm); err != nil {
			desc := fmt.Sprintf("mkdir all error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		cur, err := os.Create(filePath)
		defer func() {
			cur.Close()
		}()
		if err != nil {
			desc := fmt.Sprintf("error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		log.Infof("filePath: %s", filePath)
		// 把上传文件数据拷贝到我们新建的文件
		_, _ = io.Copy(cur, file)

		host := fmt.Sprintf("http://%s:%d", qfcfg.ServerConfig.Server.IP, qfcfg.ServerConfig.Server.Port)
		// 如果使能了http下载文件使用代理，则使用AgentHost
		if qfcfg.ServerConfig.Server.Enable != 0 {
			host = qfcfg.ServerConfig.Server.AgentHost
		}

		t := &token.TokenCreator{}
		reportToken := ReportToken{DirPath: uploadDirPth}
		config := qfcfg.ServerConfig.Server.Report.TokenConfig
		expire, _ := time.ParseDuration(config.Expire)

		tokenVal, err := t.SetKey(config.Key).SetExpired(expire).GenHashToken(reportToken)
		if err != nil {
			desc := fmt.Sprintf("error: %v", err)
			log.Errorf("%s", desc)
			JSONResp(c, http.StatusOK, desc)
			return
		}

		token := tokenVal
		if qfcfg.ServerConfig.Server.Auth.Enable > 0 {
			token = strings.ToUpper(util.GenShortUUID())
			if err := qfredis.SetDigestInfo(c, token, tokenVal, int(expire.Seconds())); err != nil {
				log.Errorf("set token to redis error: %v", err)
				JSONResp(c, http.StatusOK, err.Error())
				return
			}
		}

		outPath, err := transDocs2Pdf(c, filePath, fileDirPath, outFromat, token)
		if err != nil {
			log.Errorf("trans file err: %v", err)
			JSONResp(c, http.StatusOK, err.Error())
			return
		}

		var uriPath string
		pos = strings.Index(outPath, uploadDirPth)
		if -1 != pos {
			uriPath = outPath[pos:]
		}

		msg := fmt.Sprintf("[INFO] file:%s convert succeed, path:%s/%s?token=%s",
			filePath, host, uriPath, token)
		JSONResp(c, http.StatusOK, msg)
	}
}

// NoticeHandler http
func (control Controller) NoticeHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	if c.Method() != "GET" {
		c.StatusCode(http.StatusNotFound)
		log.Error("404")
		return
	}

	log.Infof("uri: %s method: %s", c.Request().RequestURI, c.Method())
	JSONResp(c, http.StatusOK, qfcfg.ServerConfig.Server.Notice)
}

// DownloadHandler http
func (control Controller) DownloadHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	if c.Method() != "GET" {
		c.StatusCode(http.StatusNotFound)
		log.Error("404")
		return
	}

	if binPath == "" {
		var err error
		binPath, err = os.Getwd()
		if err != nil {
			log.Errorf("error: %v", err)
		}
		log.Infof("binPath: %s", binPath)
	}

	uri := c.Request().RequestURI
	if strings.Contains(uri, "?token=") {
		uri = strings.Split(uri, "?")[0]
	}
	filePath := binPath + uri
	log.Infof("GET filePath: %s", filePath)
	isExist, _ := pathFileExists(filePath)

	if isExist {
		// 设置响应的header头
		filename := c.Params().Get("filename")
		if strings.Contains(filename, "?token=") {
			filename = strings.Split(filename, "?")[0]
		}
		c.Header("Content-type", "application/octet-stream")
		c.Header("content-disposition", "attachment; filename=\""+filename+"\"")
		log.Infof("filename: %s", filename)

		if err := c.SendFile(filePath, filename); err != nil {
			c.StatusCode(http.StatusBadRequest)
			return
		}
	} else {
		c.StatusCode(http.StatusNotFound)
		log.Error("404")
	}
}

// LockHandler def
func (control Controller) LockHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	// lock
	if lock := qfredis.QFCommonLocker.Lock(c); !lock {
		info := "lock has been dispatched other, please wait"
		log.Errorf("%s", info)
		JSONResp(c, http.StatusOK, info)
		return
	}
	// do sth.
	time.Sleep(6 * time.Second)
	info := "dispatch lock to this"
	log.Infof("%s", info)
	JSONResp(c, http.StatusOK, info)

	defer qfredis.QFCommonLocker.UnLock(c)
}

// RedisHandler http
func (control Controller) RedisHandler(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	c.StatusCode(http.StatusOK)

	val, err := qfredis.MembersIPInfo(c, "mts_10.8.5.161")
	if err != nil {
		log.Errorf("error: %v", err)
		c.StatusCode(http.StatusBadRequest)
		return
	}

	var value string
	for _, v := range val {
		value = v
		log.Infof("value: %s", value)
	}
	info := fmt.Sprintf("value: %s\n", value)
	_, _ = c.Text(info)
}

func (control Controller) execShell(c context.Context, cmdStr string) string {
	log := log.GetLogger().GetCtx(c).Sugar()

	cmd := exec.Command("/bin/bash", "-c", cmdStr)
	out, err := cmd.CombinedOutput()
	if err != nil {
		log.Errorf("err: %v", err)
	}
	output := strings.Trim(string(out), "\n")
	log.Infof("reportCmd output: %s", output)
	return output
}

// IperfHandler http
func (control Controller) IRedMailQueue(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	if c.Method() != "GET" {
		c.StatusCode(http.StatusNotFound)
		log.Error("404")
		return
	}
	output := control.execShell(c.Request().Context(), "/opt/scripts/queue.sh")
	log.Infof("uri: %s method: %s", c.Request().RequestURI, c.Method())
	JSONResp(c, http.StatusOK, output)
}

// Pagination 包含了分页数据。
type Pagination struct {
	// NextPage 是下一页数据的页数。若没有下一页，则为 nil。
	NextPage *int `json:"nextPage"`
}

// GetTask http
func (control Controller) GetTask(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	uid := c.Params().Get("uid")
	if uid == "" {
		log.Error("task uid is empty")
		JSONError(c, http.StatusBadRequest, "task uid is empty")
		return
	}

	result, err := db.Tasks.Get(c, uid)
	if err != nil {
		log.Errorf("task get err: %v ", err)
		JSONError(c, http.StatusBadRequest, "task get err")
		return
	}

	type TaskInfo struct {
		UID       string                 `json:"uid"`
		Status    string                 `json:"status"`
		Metadata  map[string]interface{} `json:"metadata"`
		CreatedAt time.Time              `json:"created_at"`
		UpdatedAt time.Time              `json:"updated_at"`
	}
	var resp = struct {
		Task []*TaskInfo `json:"task"`
	}{
		Task: func() (ss []*TaskInfo) {
			ss = make([]*TaskInfo, 0, len(result.TasksInfo))
			for _, t := range result.TasksInfo {
				ss = append(ss, &TaskInfo{
					UID:       t.UID,
					Status:    t.Status,
					Metadata:  t.MetaData,
					CreatedAt: t.CreatedAt,
					UpdatedAt: t.UpdatedAt,
				})
			}
			return ss
		}(),
	}

	JSONResp(c, http.StatusOK, resp)
}

// GetTask http
func (control Controller) GetTaskV2(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	sorts := c.URLParamSlice("sorts[]")
	opts := db.TaskGetOptions{
		TaskID:   c.URLParamDefault("taskID", ""),
		TaskName: c.URLParamDefault("taskName", ""),
		Sorts:    util.DealSorts(sorts),
		PageSize: func() int {
			size := c.URLParamIntDefault("pageSize", 0)
			if size == 0 {
				size = common.DefaultPageSize
			}
			return size
		}(),
		Page: func() int {
			page := c.URLParamIntDefault("page", 0)
			if page == 0 {
				page = common.DefaultPage
			}
			return page
		}(),
	}

	// 校验排序字段是否合法
	if len(opts.Sorts) > 0 {
		sortFields := []string{"created_at"}
		validCnt := 0
		for _, item := range opts.Sorts {
			field := strings.Split(item, " ")[0]
			for _, e := range sortFields {
				if field == e {
					validCnt++
					break
				}
			}
		}

		if validCnt < len(opts.Sorts) {
			log.Error("err: invalid sort field")
			JSONError(c, http.StatusBadRequest, "invalid sort field")
		}
	}

	result, nextPage, err := db.Tasks.GetV2(c, opts)
	if err != nil {
		log.Errorf("task info get err: %v", err)
		JSONError(c, http.StatusInternalServerError, "task get err")
		return
	}

	type TaskInfo struct {
		UID       string                 `json:"uid"`
		Status    string                 `json:"status"`
		Metadata  map[string]interface{} `json:"metadata"`
		CreatedAt time.Time              `json:"created_at"`
		UpdatedAt time.Time              `json:"updated_at"`
	}
	var resp = struct {
		Total      *int        `json:"total"`
		Task       []*TaskInfo `json:"task"`
		UpdatedAt  time.Time   `json:"updatedAt"`
		Pagination Pagination  `json:"pagination"`
	}{
		Total: result.Total,
		Task: func() (ss []*TaskInfo) {
			ss = make([]*TaskInfo, 0, len(result.TasksInfo))
			for _, t := range result.TasksInfo {
				ss = append(ss, &TaskInfo{
					UID:       t.UID,
					Status:    t.Status,
					Metadata:  t.MetaData,
					CreatedAt: t.CreatedAt,
					UpdatedAt: t.UpdatedAt,
				})
			}
			return ss
		}(),
		Pagination: Pagination{
			NextPage: nextPage,
		},
	}

	JSONResp(c, http.StatusOK, resp)
}

type ipInfoQuery struct {
	IP string `url:"ip"`
}

// GetIPInfo def
func (control Controller) GetIPInfo(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()
	var q ipInfoQuery

	if err := c.ReadQuery(&q); err != nil && !iris.IsErrPath(err) {
		log.Errorf("read query err: %v", err)
		JSONError(c, http.StatusBadRequest, "read query error")
		return
	}

	controller, err := newIPInfoController(qfcfg.ServerConfig.Server.IPInfo.Mode)
	if err != nil {
		log.Errorf("new IPInfoController err: %v", err)
		JSONError(c, http.StatusBadRequest, err.Error())
		return
	}

	ipInfo, err := controller.GetIPInfo(q.IP)
	if err != nil {
		log.Errorf("new IPInfoController err: %v", err)
		JSONError(c, http.StatusBadRequest, err.Error())
		return
	}

	JSONResp(c, http.StatusOK, ipInfo)
}

type taskRequest struct {
	UID        string     `json:"uid"`
	Status     StatusType `json:"status"`
	TaskName   string     `json:"taskName"`
	PassRate   string     `json:"passRate"`
	ReportPath string     `json:"reportPath"`
	LogPath    string     `json:"logPath"`
	HtmlPath   string     `json:"htmlPath"`
}

func checkUpsertTaskParams(c iris.Context, req taskRequest) error {
	if req.UID == "" {
		return errors.New("task uid is empty")
	}

	if req.TaskName == "" {
		return errors.New("task name is empty")
	}

	if req.Status == "" {
		return errors.New("task statsus is empty")
	}

	return nil
}

// UpsertTask http
func (control Controller) UpsertTask(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	var req taskRequest
	if err := util.TransJsonReq2Struct(c, &req); err != nil {
		log.Errorf("trans json to struct fail, err: %v", err)
		JSONError(c, http.StatusBadRequest, "trans json to struct error")
		return
	}

	if err := checkUpsertTaskParams(c, req); err != nil {
		log.Errorf("err: %v", err)
		JSONError(c, http.StatusBadRequest, err.Error())
		return
	}

	// 先查询 task 是否已存在
	result, err := db.Tasks.Get(c, req.UID)
	if err != nil {
		log.Errorf("task get err: %v ", err)
		JSONError(c, http.StatusBadRequest, "task get err")
		return
	}

	taskInfo := map[string]interface{}{
		"taskName":   req.TaskName,
		"passRate":   req.PassRate,
		"reportPath": req.ReportPath,
		"logPath":    req.LogPath,
		"htmlPath":   req.HtmlPath,
	}

	opts := db.TasksUpdateOptions{
		UID:      req.UID,
		Status:   string(req.Status),
		MetaData: taskInfo,
	}

	if len(result.TasksInfo) == 0 {
		err = db.Tasks.Create(c, opts)
	} else {
		err = db.Tasks.Update(c, opts)
	}
	if err != nil {
		log.Errorf("task upsert err: %v ", err)
		JSONError(c, http.StatusBadRequest, "task upsert err")
		return
	}

	JSONResp(c, http.StatusOK, "task upsert succeed")
}

type size struct {
	Width  int64 `json:"width"`
	Height int64 `json:"height"`
}

type screenShotRequest struct {
	URL      string `json:"url"`
	SaveName string `json:"saveName"`
	Quality  int64  `json:"quality"`
	Window   size   `json:"window"`
	Region   size   `json:"region"`
}

func checkScreenShotParams(c iris.Context, req *screenShotRequest) error {
	if req.URL == "" {
		return errors.New("url is empty")
	}

	if req.SaveName == "" {
		req.SaveName = "screenshot.png"
	}

	if req.Window.Width < 0 || req.Window.Width > 4096 {
		req.Window.Width = 1920
	}

	if req.Window.Height < 0 || req.Window.Height > 2160 {
		req.Window.Height = 1080
	}

	if req.Region.Width < 0 || req.Region.Width > 4096 {
		return errors.New("region width must be range 0 to 4096")
	}

	if req.Region.Height < 0 || req.Region.Height > 2160 {
		return errors.New("region height must be range 0 to 2160")
	}

	if req.Quality < 1 || req.Quality > 100 {
		req.Quality = 90
	}

	return nil
}

// Screenshot def
func (control Controller) Screenshot(c iris.Context) {
	log := log.GetLogger().GetCtx(c.Request().Context()).Sugar()

	var req screenShotRequest
	if err := util.TransJsonReq2Struct(c, &req); err != nil {
		log.Errorf("trans json to struct fail, err: %v", err)
		JSONError(c, http.StatusBadRequest, "trans json to struct error")
		return
	}

	if err := checkScreenShotParams(c, &req); err != nil {
		log.Errorf("err: %v", err)
		JSONError(c, http.StatusBadRequest, err.Error())
		return
	}

	if ret := sema.TryAcquire(1); !ret {
		log.Error("sema try acquire failed")
		JSONError(c, http.StatusBadRequest, "screenshot handler is busy, please try again later")
		return
	}

	cfg := util.ScreenShotConfig{
		URL:      req.URL,
		SaveName: req.SaveName,
		SaveDir:  fmt.Sprintf("%s/screenshot/%s", qfcfg.ServerConfig.Server.FileSvr.Dir, c.GetHeader(common.TraceID)),
		Window: util.Size{
			Width:  req.Window.Width,
			Height: req.Window.Height,
		},
		Region: util.Size{
			Width:  req.Region.Width,
			Height: req.Region.Height,
		},
		Quality: req.Quality,
	}

	filePath, err := util.Screenshot(c.Request().Context(), cfg)
	if err != nil {
		log.Errorf("screenshot err: %v", err)
		JSONError(c, http.StatusBadRequest, "screenshot error")
		sema.Release(1)
		return
	}

	host := fmt.Sprintf("http://%s:%d", qfcfg.ServerConfig.Server.IP, qfcfg.ServerConfig.Server.Port)
	// 如果使能了http下载文件使用代理，则使用AgentHost
	if qfcfg.ServerConfig.Server.Enable != 0 {
		host = qfcfg.ServerConfig.Server.AgentHost
	}

	uriPath := strings.ReplaceAll(filePath, "./", "")
	picUrl := fmt.Sprintf("%s/%s", host, uriPath)
	JSONResp(c, http.StatusOK, picUrl)
	sema.Release(1)
}
